﻿using System;
using System.Collections.Generic;
using System.Text;

namespace PCR.Logic
{
    using PCR.Base;

    /// <summary>
    /// Provide all the calculation methods for whole application
    /// </summary>
    internal class Calculator : ICalculator
    {
        /// <summary>
        /// Get the standard curve param from the the sample data
        /// The sample data count must more than 2
        /// </summary>
        /// <returns>null if the data is not correct </returns>
        public IStdCurveParam GetStdParamFromSampleData(IList<IStdSampleData> samples)
        {
            System.Diagnostics.Debug.Assert(samples != null);
            if (samples.Count < 2)
            {
                PCRRoot.Instance.LogManager.GetLogger(typeof(Calculator)).Error(
                    "Failed to calculate standard curve parameter : sample count less than 2");
                
                return null;
            }

            int iCnt = 0;
            double sumX = 0, sumY = 0, sumXX = 0, sumXY = 0, sumYY = 0;
            foreach (IStdSampleData sample in samples)
            {
                double logx = Math.Log10(sample.CellCount);
                sumX += logx;
                sumY += sample.CTVal;

                sumXX += logx * logx;
                sumXY += logx * sample.CTVal;

                sumYY += sample.CTVal * sample.CTVal;
                iCnt++;
            }

            // Check data
            if (sumXX - sumX * sumX / samples.Count == 0)
                return null;

            double kVal, bVal, eVal, rVal, r2Val;

            // Get k, b
            kVal = (sumXY - sumX * sumY / samples.Count) / (sumXX - sumX * sumX / samples.Count);
            bVal = kVal * (-1) * sumX / samples.Count + sumY / samples.Count;

            // Generate Y' Value list
            double[] Y = new double[iCnt];
            for (int i = 0; i < iCnt; i++)
            {
                Y[i] = kVal * Math.Log10(samples[i].CellCount) + bVal;
            }

            //R*R = 1 - SSE/SST, where SSE = SUM[(Yi-Yi')*(Yi-Yi')],SST = SUM(Yi*Yi)-SUM(Yi)*SUM(Yi)/n;
            double sumDeltaY = 0;
            for (int j = 0; j < iCnt; j++)
            {
                sumDeltaY += (samples[j].CTVal - Y[j]) * (samples[j].CTVal - Y[j]);
            }

            // Get r2, r
            r2Val = 1 - sumDeltaY / (sumYY - sumY * sumY / iCnt);
            rVal = Math.Sqrt(r2Val);

            // Get e
            eVal = Math.Pow(10, -1 / kVal) - 1;

            kVal = GetRound(kVal);
            bVal = GetRound(bVal);
            eVal = GetRound(eVal);
            rVal = GetRound(rVal);
            r2Val = GetRound(r2Val);

            return new StdCurveParam(kVal, bVal, eVal, rVal, r2Val);
        }

        /// <summary>
        /// Get the patient's virus revised CT by 
        /// patient virus parameter, standard curve parameter, virus type parameter
        /// </summary>
        /// <param name="patientVirus">Patient virus data</param>
        /// <param name="curveParam">Standard curve parameter</param>
        /// <param name="virusType">Virus type parameter</param>
        /// <param name="virusType">Pass out the patient's revised virus</param>
        /// <returns>true if successfully, false if failed</returns>
        public bool GetVirusRevisedCT(IPatientVirus patientVirus, IStdCurveParam curveParam,
            IVirusType virusType, out double revisedCT)
        {
            System.Diagnostics.Debug.Assert(patientVirus != null);
            System.Diagnostics.Debug.Assert(curveParam != null);
            System.Diagnostics.Debug.Assert(virusType != null);

            revisedCT = 0.0;

            // First calculate the reference cell count from the standard curve
            double refCellCount = 0.0;
            if (!GetCellCountFromStdCurve(curveParam, patientVirus.ReferCellCTVal, out refCellCount))
                return false;
            
            UInt32 baseCellCount = PCRRoot.Instance.VirusTypeManager.BaseCellCount;
            if (baseCellCount == 0)
                return false;

            // Then calculate the virus revised CT
            revisedCT = patientVirus.VirusCTVal + Math.Log(refCellCount / baseCellCount, 1 + virusType.EVal);
            
            revisedCT = GetRound(revisedCT);

            return true;
        }

        /// <summary>
        /// Get the patient's virus exponent by 
        /// patient virus parameter, standard curve parameter, virus type parameter
        /// </summary>
        /// <param name="patientVirus">Patient virus data</param>
        /// <param name="curveParam">Standard curve parameter</param>
        /// <param name="virusType">Virus type parameter</param>
        /// <param name="virusType">Pass out the patient's virus exponent</param>
        /// <returns>true if successfully, false if failed</returns>
        public bool GetVirusExponent(IPatientVirus patientVirus, IStdCurveParam curveParam,
            IVirusType virusType, out double exponent)
        {
            exponent = 0.0; // Default value, also a error value
            
            // First get the virus revised CT
            double revisedVirusCT;
            if (!GetVirusRevisedCT(patientVirus, curveParam, 
                    virusType, out revisedVirusCT))
                return false;

            // Then calculate the infect exponent
            // 感染单位= 10^(Kv * CT + Bv)
            exponent = Math.Pow(10, virusType.KVal * revisedVirusCT + virusType.BVal);
 
            exponent = Math.Round(exponent, 1);

            return true;
        }

        /// <summary>
        /// Get the revised CT by the standard curve parameter
        /// </summary>
        /// <param name="kVal">Standard curve K value</param>
        /// <param name="bVal">Standard curve B value</param>
        /// <param name="cellCount">Cell count</param>
        /// <param name="revisedCT">Passed out revised CT value</param>
        /// <returns>true if OK, false if failed</returns>
        public double GetRevisedCT(double kVal, double bVal, double cellCount)
        {
            return GetRound(kVal * Math.Log10(cellCount) + bVal);
        }
        
        protected bool GetCellCountFromStdCurve(IStdCurveParam curveParam, double referCellCT, out double cellCount)
        {
            System.Diagnostics.Debug.Assert(curveParam != null);

            cellCount = 0.0;

            // y = kx + b;
            // x = (y - b) / k;
            if (curveParam.KVal == 0.0)
                return false;

            double logCellCount = (referCellCT - curveParam.BVal) / curveParam.KVal;
            cellCount = Math.Pow(10, logCellCount);
            
            return true;
        }

        private double GetRound(double value)
        {
            return Math.Round(value, PCRRoot.Instance.ConfigManager.DigitialPrecision);
        }
    }
}
